/*
 * +----------------------------------------------------------------------
 * | Copyright (c) 奇特物联 2021-2022 All rights reserved.
 * +----------------------------------------------------------------------
 * | Licensed 未经许可不能去掉「奇特物联」相关版权
 * +----------------------------------------------------------------------
 * | Author: xw2sy@163.com
 * +----------------------------------------------------------------------
 */
package cc.iotkit.plugins.soulcall.service;

import cc.iotkit.model.device.DeviceInfo;
import cc.iotkit.plugin.core.thing.IThingService;
import cc.iotkit.plugin.core.thing.actions.DeviceState;
import cc.iotkit.plugin.core.thing.actions.up.DeviceRegister;
import cc.iotkit.plugin.core.thing.actions.up.DeviceUnregister;
import cc.iotkit.plugin.core.thing.actions.up.PropertyReport;
import cc.iotkit.plugins.soulcall.conf.SoulcallConfig;
import cc.iotkit.plugins.soulcall.parse.DataPackage;
import cn.hutool.core.util.IdUtil;
import com.gitee.starblues.bootstrap.annotation.AutowiredType;
import com.gitee.starblues.core.PluginInfo;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServer;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.client.WebClient;
import io.vertx.ext.web.client.WebClientOptions;
import io.vertx.ext.web.handler.BodyHandler;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * soulcall官方协议文档：
 *
 *
 * @author lishj
 */
@Slf4j
@Component
@Data
public class SoulcallVerticle extends AbstractVerticle{

    private SoulcallConfig config;

    @Autowired
    @AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
    private IThingService thingService;

//    @Autowired
//    @AutowiredType(AutowiredType.Type.MAIN_PLUGIN)
//    private OpenDeviceService openDeviceService;

    @Autowired
    private PluginInfo pluginInfo;

    private static final Set<String> DEVICE_ONLINE = new HashSet<>();

    private final Map<String, Integer> existDevice = new ConcurrentHashMap<>();


    private WebClient webClient;
    private HttpServer httpServer;




    @Override
    public void start() {
        WebClientOptions options = new WebClientOptions()
                .setUserAgent("potevio-App/1.0.0");
        options.setKeepAlive(true);
        webClient = WebClient.create(vertx, options);
        Router router = Router.router(vertx);
        router.route().handler(BodyHandler.create());
        router.route(HttpMethod.POST,LinxiSignHelper.ALARM_PATH).handler(this::handleAlarmRequest);
        router.route(HttpMethod.POST,LinxiSignHelper.EVENT_PATH).handler(this::handleEventRequest);
        router.route(HttpMethod.POST,LinxiSignHelper.ADD_DEVICE).handler(this::handleAddDeviceRequest);
        router.route(HttpMethod.POST,LinxiSignHelper.DEL_DEVICE).handler(this::handleDelDeviceAlarmRequest);
        httpServer = vertx.createHttpServer();
        httpServer.requestHandler(router)
                .listen(config.getPort(),ar->{
                    if(ar.succeeded()){
                        log.info("SoulcallVerticle bind port success, port:"+config.getPort());
                    }else{
                        log.info("SoulcallVerticle bind port failure,"+ar.cause().toString());
                    }
                });
    }

    /**
     * 解绑设备
     * @param ctr
     */
    private void handleDelDeviceAlarmRequest(RoutingContext ctr) {
        String deviceId = ctr.request().getParam("deviceId");
        if(StringUtils.isEmpty(deviceId)){
            ctr.response().setStatusCode(506).end("设备ID不正确");
            return;
        }else{
        //注销设备
        thingService.post(pluginInfo.getPluginId(), DeviceUnregister.builder()
                .id(IdUtil.simpleUUID())
                .productKey(config.getPk())
                .deviceName(deviceId)
                .build());
//        ctr.response().setStatusCode(200).end("成功！！！！！！");
        log.info("设备注销-------------deviceId:"+ deviceId);
        }
        webClient.post(80, "adminapi.lingxi.vodaiot.com", LinxiSignHelper.DEL_DEVICE).sendJson(ctr.getBodyAsJson(),ar->{
            if(ar.succeeded()){
                log.info(ar.toString());
                ctr.response().setStatusCode(ar.result().statusCode()).end(ar.result().bodyAsBuffer());
            }else{
                ctr.response().setStatusCode(500).end();
            }
        });
    }

    /**
     * 绑定设备
     * @param ctr
     */
    private void handleAddDeviceRequest(RoutingContext ctr) {
        String deviceId = ctr.request().getParam("deviceId");
        String deptName = ctr.request().getParam("deptName");
//        String tenantId = ctr.request().getParam("tenantId");
        if(StringUtils.isEmpty(deviceId)){
            ctr.response().setStatusCode(506).end("设备ID不正确");
            return;
        }else{
            //注册设备
            thingService.post(pluginInfo.getPluginId(), DeviceRegister.builder()
                    .id(IdUtil.simpleUUID())
                    .productKey(config.getPk())
                    .deviceName(deviceId).deptName(deptName)
                    .state(DeviceState.ONLINE)
                    .build());
//            ctr.response().setStatusCode(200).end("成功！！！！！！");
            log.info("设备注册-------------deviceId:"+ deviceId);
        }
        webClient.post(80, "adminapi.lingxi.vodaiot.com", LinxiSignHelper.ADD_DEVICE).sendJson(ctr.getBodyAsJson(),ar->{
            if(ar.succeeded()){
                ctr.response().setStatusCode(ar.result().statusCode()).end(ar.result().bodyAsBuffer());
            }else{
                ctr.response().setStatusCode(500).end();
            }
        });
    }

    /**
     * 事件信息处理
     * @param ctr
     */
    private void handleEventRequest(RoutingContext ctr) {
        JsonObject devInfo = ctr.getBodyAsJson();
        DataPackage alarmData = initEventData(devInfo);
        //不在设备列表中
        if (!checkDeviceExist(ctr, alarmData))
            return;
        //属性上报
        thingService.post(pluginInfo.getPluginId(), PropertyReport.builder()
                .id(IdUtil.simpleUUID())
                .productKey(config.getPk())
                .deviceName(alarmData.getDeviceId())
                .params(alarmData.getDataMap())
                .time(System.currentTimeMillis())
                .state(DeviceState.ONLINE)
                .build());
        webClient.post(config.getProxyPort(), "localhost", LinxiSignHelper.EVENT_PATH).sendJson(ctr.getBodyAsJson(),ar->{
            if(ar.succeeded()){
                ctr.response().setStatusCode(ar.result().statusCode()).end(ar.result().bodyAsBuffer());
            }else{
                ctr.response().setStatusCode(500).end();
            }
        });
    }

    private DataPackage initEventData(JsonObject devInfo) {
        DataPackage dataPackage = new DataPackage();
        dataPackage.setDeviceId(devInfo.getString("deviceId"));
//        dataPackage.setLv(devInfo.getInteger("lv"));
//        dataPackage.setBegin_time(devInfo.getString("start_time"));
//        dataPackage.setAlarm(devInfo.toString());
        Map<String,Object> dataMap = new HashMap<>();
        dataMap.put("event",devInfo.toString());
        dataPackage.setDataMap(dataMap);
        return dataPackage;

    }

    /**
     * 告警信息处理接口
     * @param ctr
     */
    private void handleAlarmRequest(RoutingContext ctr) {
        JsonObject devInfo = ctr.getBodyAsJson();
        DataPackage alarmData = initAlarmData(devInfo);
        //不在设备列表中
        if (!checkDeviceExist(ctr, alarmData))
            return;
        //属性上报
        thingService.post(pluginInfo.getPluginId(), PropertyReport.builder()
                .id(IdUtil.simpleUUID())
                .productKey(config.getPk())
                .deviceName(alarmData.getDeviceId())
                .params(alarmData.getDataMap())
                .time(System.currentTimeMillis())
                .state(DeviceState.ONLINE)
                .build());
        webClient.post(config.getProxyPort(), "localhost", LinxiSignHelper.ALARM_PATH).sendJson(ctr.getBodyAsJson(),ar->{
            if(ar.succeeded()){
                ctr.response().setStatusCode(ar.result().statusCode()).end(ar.result().bodyAsBuffer());
            }else{
                ctr.response().setStatusCode(500).end();
            }
        });
    }

    private boolean checkDeviceExist(RoutingContext ctr, DataPackage alarmData) {
        if(!existDevice.containsKey(alarmData.getDeviceId())){
            vertx.executeBlocking(future -> {
                DeviceInfo deviceInfo = thingService.getDevice(alarmData.getDeviceId());
                String result = "yes";
                if(Objects.nonNull(deviceInfo)){
                    existDevice.put(alarmData.getDeviceId(),1);
                }else{
                    existDevice.put(alarmData.getDeviceId(),0);
                    result = "no";
                }
                future.complete(result);
            }, res -> {
                log.info("The existDevice check result is-------deviceName:" + alarmData.getDeviceId()+" exist:"+res.result());
            });
        }else if(existDevice.containsKey(alarmData.getDeviceId()) && existDevice.get(alarmData.getDeviceId())==0){
            log.error("The device is not exist-------deviceName:" + alarmData.getDeviceId());
            ctr.response().setStatusCode(200).end();
            return false;
        }
        return true;
    }

    private DataPackage initAlarmData(JsonObject devInfo) {
        DataPackage dataPackage = new DataPackage();
        dataPackage.setDeviceId(devInfo.getString("deviceId"));
//        dataPackage.setLv(devInfo.getInteger("lv"));
//        dataPackage.setBegin_time(devInfo.getString("start_time"));
//        dataPackage.setAlarm(devInfo.toString());
        Map<String,Object> dataMap = new HashMap<>();
        dataMap.put("lv",devInfo.getInteger("lv"));
        dataMap.put("start_time",devInfo.getString("start_time"));
        dataMap.put("alarm",devInfo.toString());
        dataPackage.setDataMap(dataMap);
        return dataPackage;
    }

    @Scheduled(fixedRate = 60, timeUnit = TimeUnit.SECONDS)
    private void deviceExistCheckTask() {
        log.info("============================deviceExistCheckTask================================");
        log.info("existDevice:"+existDevice.toString());
        existDevice.keySet().iterator().forEachRemaining(addr -> {
            DeviceInfo deviceInfo =  thingService.getDevice(addr);
            if(Objects.nonNull(deviceInfo)){
                existDevice.put(addr,1);
            }else{
                existDevice.put(addr,0);
            }
        });
        log.info("new existDevice:"+existDevice.toString());
    }


    @Override
    public void stop() {
        if(Objects.nonNull(httpServer)){
            httpServer.close();
        }
    }
}
